use super::*;

/// Represents a JavaScript environment (napi_env)
pub struct Env;

impl Env {
	/// Tries to convert from napi_env to Env reference
	pub fn try_from<'env>(value: sys::napi_env) -> Option<&'env Self> {
		if value.is_null() {
			None
		} else {
			Some(unsafe { &*(value as *const Self) })
		}
	}

	/// Converts from napi_env to Env reference (panics if null)
	pub fn from<'env>(value: sys::napi_env) -> &'env Self {
		if value.is_null() {
			panic!("from null sys")
		}
		unsafe { &*(value as *const Self) }
	}

	/// Gets the raw napi_env pointer
	pub fn raw(&self) -> sys::napi_env {
		self as *const Self as *const core::ffi::c_void as *mut core::ffi::c_void as _
	}

	/// Gets the global object
	pub fn global(&self) -> Result<'_, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_get_global(self.raw(), &mut ret) }.result("napi_get_global")?;
		Ok(Value::new(self, ret))
	}

	/// Gets the null value
	pub fn null(&self) -> Result<'static, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_get_null(self.raw(), &mut ret) }.result("napi_get_null")?;
		Ok(Value::new(self, ret))
	}

	/// Gets the undefined value
	pub fn undefined(&self) -> Result<'static, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_get_undefined(self.raw(), &mut ret) }.result("napi_get_undefined")?;
		Ok(Value::new(self, ret))
	}

	/// Gets the N-API version
	pub fn version(&self) -> Result<'_, u32> {
		let mut ret = 0;
		unsafe { sys::napi_get_version(self.raw(), &mut ret) }.result("napi_get_version")?;
		Ok(ret)
	}

	/// Creates a new instance of a class
	pub fn instance<T: NapiClass, A: ArgsBuilder>(&self, args: A) -> Result<'static, Value<'_>> {
		let Some(constructor) = T::class() else {
			return Err(Status::invalid_arg("cannot find constructor"));
		};
		let constructor = constructor.into_napi(self)?;
		let args = args.into_args(self)?;
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_new_instance(self.raw(), constructor.raw(), args.len(), args.as_ptr(), &mut ret) }
			.result("napi_new_instance")?;
		Ok(Value::new(self, ret))
	}

	/// Loads a module
	/// https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-load-module
	pub fn load(&self, path: &core::ffi::CStr) -> Result<'_, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_load_module(self.raw(), path.as_ptr(), &mut ret) }.result("napi_load_module")?;
		Ok(Value::new(self, ret))
	}
	/// Loads a module with information
	/// https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/use-napi-load-module-with-info
	pub fn load_with_info(&self, path: &core::ffi::CStr, module: &core::ffi::CStr) -> Result<'_, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_load_module_with_info(self.raw(), path.as_ptr(), module.as_ptr(), &mut ret) }
			.result("napi_load_module_with_info")?;
		Ok(Value::new(self, ret))
	}
	/// Runs a abc file
	/// const char *scriptPath = "/entry/src/main/resources/rawfile/test.abc";
	/// es2abc.exe test.js --output test.abc;
	pub fn run_script(&self, path: &core::ffi::CStr) -> Result<'_, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_run_script_path(self.raw(), path.as_ptr(), &mut ret) }.result("napi_run_script_path")?;
		Ok(Value::new(self, ret))
	}

	/// Defines a JavaScript class
	pub fn define_class(
		&self,
		name: &core::ffi::CStr,
		constructor: sys::napi_callback,
		properties: &[PropertyDescriptor],
	) -> Result<'_, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe {
			sys::napi_define_class(
				self.raw(),
				name.as_ptr(),
				name.count_bytes(),
				constructor,
				core::ptr::null_mut(),
				properties.len(),
				properties.as_ptr() as _,
				&mut ret,
			)
		}
		.result("napi_define_class")?;
		Ok(Value::new(self, ret))
	}

	/// Gets the last error information
	pub fn last_error(&self) -> Result<'_, ErrorInfo> {
		let mut ret = core::ptr::null();
		unsafe { sys::napi_get_last_error_info(self.raw(), &mut ret) }.result("napi_get_last_error_info")?;
		let Some(ret) = (unsafe { ret.as_ref() }) else {
			return Err(Status::generic_failure("last error info is null"));
		};
		Ok(ErrorInfo {
			message: unsafe { std::ffi::CString::from_raw(ret.error_message as _) },
			code: ret.error_code,
			engine_code: ret.engine_error_code,
		})
	}

	/// Checks if an exception is pending
	pub fn is_exception_pending(&self) -> Result<'_, bool> {
		let mut ret = false;
		unsafe { sys::napi_is_exception_pending(self.raw(), &mut ret) }.result("napi_is_exception_pending")?;
		Ok(ret)
	}

	/// Gets and clears the last exception
	pub fn get_and_clear_last_exception(&self) -> Result<'_, Value<'_>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_get_and_clear_last_exception(self.raw(), &mut ret) }.result("napi_get_and_clear_last_exception")?;
		Ok(Value::new(self, ret))
	}

	/// Executes code within a handle scope
	pub fn with_scope<'scope, 'env: 'scope>(&'env self, callback: impl FnOnce(&'scope Env)) -> Result<'static, ()> {
		pub struct Scope<'val>(&'val Env, sys::napi_handle_scope);
		impl Drop for Scope<'_> {
			fn drop(&mut self) {
				unsafe { sys::napi_close_handle_scope(self.0.raw(), self.1) }
					.result("napi_close_handle_scope")
					.throw(self.0);
			}
		}
		let mut scope = core::ptr::null_mut();
		unsafe { sys::napi_open_handle_scope(self.raw(), &mut scope) }.result("napi_create_scope")?;
		let _scope = Scope(self, scope);
		callback(self);
		Ok(())
	}

	/// Executes code within an escapable handle scope
	pub fn with_escapable_scope<'scope, 'env: 'scope>(
		&'env self,
		callback: impl FnOnce(&'scope Env) -> Result<'static, Value<'scope>>,
	) -> Result<'static, Value<'env>> {
		pub struct Scope<'val>(&'val Env, sys::napi_escapable_handle_scope);
		impl Drop for Scope<'_> {
			fn drop(&mut self) {
				unsafe { sys::napi_close_escapable_handle_scope(self.0.raw(), self.1) }
					.result("napi_close_handle_scope")
					.throw(self.0);
			}
		}
		let mut scope = core::ptr::null_mut();
		unsafe { sys::napi_open_escapable_handle_scope(self.raw(), &mut scope) }.result("napi_create_escapable_scope")?;
		let _scope = Scope(self, scope);
		match callback(self) {
			Ok(v) => {
				let mut ret = core::ptr::null_mut();
				unsafe { sys::napi_escape_handle(self.raw(), scope, v.inner, &mut ret) }.result("napi_escape_handle")?;
				Ok(Value::new(self, ret))
			}
			Err(e) => Err(e),
		}
	}
}

/// Contains error information from the last operation
pub struct ErrorInfo {
	pub message: std::ffi::CString,
	pub code: sys::napi_status,
	pub engine_code: u32,
}
